#include "RbnGenomeNode.h"

#include <cstring>
using std::memcpy;

#include "../../../base.h"
#include "../../../math/Random.h"

namespace GA {

/*
	RbnGenomeNode
*/

/*	public	*/

RbnGenomeNode* RbnGenomeNode::newRandom(int nTotalInputs, int k) {
	/* random */
	Random& random = Random::getInstance();

	/* node inputs : init */
	int* inputs = new int[k]; 

	/* node intputs : fill */
	for(int i = 0; i != k; ++i) {
		inputs[i] = random.getInt(nTotalInputs);
	}

	/* function : size */
	int functionSize = 1 << k;

	/* function : init */
	int* function = new int[functionSize];
	
	/* function : fill */
	for(int i = 0; i != functionSize; ++i) {
		function[i] = random.getInt(2);
	}

	/* new rbn genome node */
	return new RbnGenomeNode(k, inputs, function);
}

RbnGenomeNode::~RbnGenomeNode() {
	delete[] _inputs;   /* inputs   */
	delete[] _function; /* function */
}

RbnGenomeNode* RbnGenomeNode::copy() const {
	return new RbnGenomeNode(*this);
}

void RbnGenomeNode::mutate(int nTotalInputs) {
	/* random */
	Random& random = Random::getInstance();

	/* mutate inputs/function : flip coin */
	if (random.getBool()) { /* mutate inputs */
		/* k */
		int k = _k;

		switch(random.getInt(3)) {

		case 0: /* change input */
			if (k != 0) {
				_inputs[random.getInt(k)] = random.getInt(nTotalInputs);
			}
			break;

		case 1: /* add input */
			{
				/* increase inputs size, putting the new input at the beginning */
				/* (for ease of function update) */
				int* newInputs = new int[k + 1];
				memcpy(newInputs + 1, _inputs, k * sizeof(int));

				/* new input */
				newInputs[0] = random.getInt(nTotalInputs);

				/* swap new inputs */
				delete[] _inputs;
				_inputs = newInputs;

				/* increase function size */
				int oldFunctionSize = 1 << k;
				int newFunctionSize = 1 << (k + 1);
				int* newFunction = new int[newFunctionSize];
				memcpy(newFunction, _function, oldFunctionSize * sizeof(int));
				
				/* add random output for when the new input is 1 */
				for(int i = oldFunctionSize; i != newFunctionSize; ++i) {
					newFunction[i] = random.getInt(2);
				}

				/* swap new function */
				delete[] _function;
				_function = newFunction;

				/* increase k */
				++_k;
			}
			break;

		case 2: /* remove input */

			/* is there anything to remove ? */
			if (k != 0) {
				/* yes */

				/* input to remove */
				int index = random.getInt(k);

				/* shift inputs following removed */
				for(int i = index + 1; i < k - 1; ++i) {
					_inputs[i] = _inputs[i + 1];
				}

				/* update function */
				int oldFunctionSize = 1 << k;
				int indexMask       = 1 << index;
				int newIndex = 0;
				for(int i = 0; i != oldFunctionSize; ++i) {
					if ((i & indexMask) == 0) {
						_function[newIndex] = _function[i];
//						assert(_function[newIndex] == 0 || _function[newIndex] == 1); //DEBUG
						++newIndex;
					}
				}
				assert(newIndex == (1 << (k - 1)));

				/* udpate k */
				--_k;
			}
		}
	} else { /* mutate function */

		if (_k != 0) {
			/* random function index */ 
			int index = random.getInt(1 << _k);

			/* flip */
			_function[index] ^= 1;
	//		assert(_function[index] == 0 || _function[index] == 1); //DEBUG
		}
	}
}

RbnNode* RbnGenomeNode::getNewRbnNode() const {
	return new RbnNode(_k, _inputs, _function);
}


/*	private	*/

RbnGenomeNode::RbnGenomeNode(int k, int* inputs, int* function) :
	_k(k),
	_inputs(inputs),
	_function(function) {}

RbnGenomeNode::RbnGenomeNode(const RbnGenomeNode& node) :
	_k(node._k) {

	/* k */
	int k = _k;

	/* inputs */
	int* inputs = new int[k];
	memcpy(inputs, node._inputs, k * sizeof(int));
	_inputs = inputs;

	/* function */
	int functionSize = 1 << k;
	int* function = new int[functionSize];
	memcpy(function, node._function, functionSize * sizeof(int));
	_function = function;
}

} // GA

